using System;
using System.Collections.Generic;
using System.Text;

namespace Microsoft.SqlServer.BISystems.Tool
{
    /// <summary>
    /// Random number generator.
    ///
    /// We return System.Random, but set the seed differently.
    /// This can allow usage of consecutive seed values.
    /// Otherwise there are bad correlations between parallel streams.
    /// </summary>
    //
    // EJ July 2007
    //
    // Note that System.Random uses Don Knuth's subtractive generator.
    // See http://www-cs-faculty.stanford.edu/~knuth/news02.html#rng, 
    // where Don Knuth describes the problem and his recommendation for 
    // initializing his subtractive RNG.
    // He reports:
    // ... noticed a new type of deficiency in this generator. 
    // He was using ran_array to generate only a few random numbers, 
    // but he was initializing it with many different seed values. 
    // This mode of usage had never been considered before in the literature, 
    // as far as either of us knew, and he found that essentially every generator 
    // he tried was giving him problems when used in such a way. 
    // ... so I asked Richard Brent for help. 
    // Working with Gimeno, he carried out elaborate tests and found two cures for the problem: 
    // (1) Discard the first 2000 numbers produced by ran_array after it has been started. 
    // (2) Improve the algorithm used by ran_start to prime the pump. 
    // With either (1) or (2), no statistical bias is detected in the 
    // resulting numbers by Marsaglia's famous "Die Hard" battery of tests. 

    [Serializable]
    public class PerfRandom_TransformSeedFunction
    {
        /// <summary>
        /// Return a System.Random with a transformed seed.
        /// </summary>
        public static System.Random CreateSystemRandomTransformSeed(int iSeed)
        {
            // Create a seed if desired.
            // Note that clock time may introduce correlation if many instances are 
            // started at the same time, since clock tick granularity is about 16 millisec.
            // One technique is for different client runs to use client number (e.g. 1, 2, 3, etc.) 
            // which we want to give a reproducibile and non-correlated sequence.
            if (iSeed <= 0)
                iSeed = Guid.NewGuid().GetHashCode();

            // Transform the seed, to spread out any sequential seed usage.
            // Note that we always transform at least once, using this simple Linear Congruential Generator.
            uint uSeed = (uint)iSeed;
            do
            {
                uSeed = 69069 * uSeed + 12345;
            } while (uSeed > (uint)Int32.MaxValue);

            System.Random r = new System.Random((int)uSeed);

            // Discard a set of values, based on the seed.
            // This is to decorrelate streams with sequential seeds
            // by shifting the order with relationship to each other.
            // Always discard at least the first 2000.

            int cDiscard = 2000 + (int)(uSeed % 1999);
            for (int j = 0; j < cDiscard; j++)
                r.Next();

            //Console.WriteLine("iSeed = {0}, uSeed = {1}, cDiscard = {2}", iSeed, uSeed, cDiscard);

            return r;
        }

        // Show a matrix of values produced with different seeds.
        public static void TestShowWithSeeds()
        {
            const int NUMSEED = 20;
            const int NUMVAL = 40;

            // For this scenario, the system RNG performs poorly (has patterns).
            Console.WriteLine("System.Random");
            for (int iSeed = 1; iSeed <= NUMSEED; iSeed++)
            {
                System.Random r = new System.Random(iSeed);
                Console.Write("{0:00}", iSeed);
                for (int j = 0; j < NUMVAL; j++)
                    Console.Write(" {0:00}", r.Next(5, 25));
                Console.WriteLine("");
            }

            Console.WriteLine("TransformWrapperFunction");
            for (int iSeed = 1; iSeed <= NUMSEED; iSeed++)
            {
                System.Random r = CreateSystemRandomTransformSeed(iSeed);
                Console.Write("{0:00}", iSeed);
                for (int j = 0; j < NUMVAL; j++)
                    Console.Write(" {0:00}", r.Next(5, 25));
                Console.WriteLine("");
            }
        }
    }
}
